using System;
using System.Collections.Generic;
using System.Linq;
using FireStarter.Base;
using FireStarter.Base.BusinessObjectMerger;
using Firestarter.DB.Schema.BO;
using FireStarterModeller.BO;
using FireStarterModeller.Logic;
using FireStarterModeller.Logic.Wizards;
using Habanero.Base;
using Habanero.BO;
using Habanero.Util;

namespace Firestarter.DB_DomainModel.Logic
{
    public interface IDBChangesImporter
    {
        IList<IBOError> ImportDBChanges(IDBDatabase database, DMSolution solution);
    }
// ReSharper disable InconsistentNaming
    internal static class IListExtentionMethod
    {
        public static IList<T> ForEach<T>(this IList<T> list, Action<T> method)
        {
            foreach (var item in list)
            {
                method(item);
            }
            return list;
        }
    }
    // ReSharper restore InconsistentNaming
    public class DBChangesImporter : IDBChangesImporter
    {
        private readonly IDBToDMImporterFactory _dbToDMImporterFactory;
        private readonly IDBToDMClassImporter _dbToDMClassImporter;
        private readonly IDBToDMPropertyImporter _dbToDMPropertyImporter;
        private readonly IDBToDMObjectIdentityImporter _dbToDMObjectIdentityImporter;
        private readonly IDBToDMUniqueConstraintImporter _dbToDMUniqueConstraintImporter;
        private readonly IDBToDMRelationshipImporter _dbToDMRelationshipImporter;

        public DBChangesImporter(IDBToDMImporterFactory dbToDMImporterFactory)
        {
            _dbToDMImporterFactory = dbToDMImporterFactory;
            _dbToDMClassImporter = _dbToDMImporterFactory.CreateDBToDMClassImporter();
            _dbToDMPropertyImporter = _dbToDMImporterFactory.CreateDBToDMPropertyImporter();
            _dbToDMObjectIdentityImporter = _dbToDMImporterFactory.CreateDBToDMObjectIdentityImporter();
            _dbToDMUniqueConstraintImporter = _dbToDMImporterFactory.CreateDBToDMUniqueConstraintImporter();
            _dbToDMRelationshipImporter = _dbToDMImporterFactory.CreateDBToDMRelationshipImporter();
        }

        public IList<IBOError> ImportDBChanges(IDBDatabase database, DMSolution solution)
        {
            if (database == null) throw new ArgumentNullException("database");
            if (solution == null) throw new ArgumentNullException("solution");
            

            UpdateSolutionName(database, solution);

             

            List<IBOError> errors = new List<IBOError>();
            foreach (DBTable table in GetBusinessObjectArray(database.Tables))
            {
                if (table.PendingChangeType == ChangeType.Add && !table.PendingChangeApproved) continue;
                ImportDBTableChanges(solution, table);

               // string solutionName = solution.SolutionName;
               // if (solution.SolutionName == null)
                    //solution.SolutionName = MustBeRenamed(solution.SolutionName);

                //if (table.GetMappedDMClass() == null) return;
                //This logic works different from other imports with other imports if the prop existed on the
                // class and it could not be found on the Table it would be deleted.
                table.Columns.ForEach(column => ImportDBColumnChanges(column, errors));
                ImportDBPrimaryKeyChanges(table, errors);

                table.Keys.ForEach(key => ImportDBKeyChanges(key, errors));
            }

            foreach (DBView view in GetBusinessObjectArray(database.Views))
            {
                if (view.PendingChangeType == ChangeType.Add && !view.PendingChangeApproved) continue;
                ImportDBTableChanges(solution, view);

                //if (table.GetMappedDMClass() == null) return;
                //This logic works different from other imports with other imports if the prop existed on the
                // class and it could not be found on the Table it would be deleted.
                view.Columns.ForEach(column => ImportDBColumnChanges(column, errors));
            }
            //foreign keys must be imported after all tables because they are dependant on multiple tables
            foreach (DBTable table in GetBusinessObjectArray(database.Tables))
            {
                if (table.PendingChangeType == ChangeType.Add && !table.PendingChangeApproved) continue;
                if (table.PendingChangeType == ChangeType.Delete) continue;
                foreach (DBForeignKey key in GetBusinessObjectArray(table.ForeignKeys))
                {
                    ImportDBForeignKeyChanges(key, errors);
                }
            }

            database.Tables.ForEach(table =>
            {
                if (table.PendingChangeApproved) table.PendingChangeType = ChangeType.None;
            });

            if (database.PendingChangeApproved) database.PendingChangeType = ChangeType.None;

            DMSolutionPostLoadProcessor processor = new DMSolutionPostLoadProcessor(new AutoResolvingUnityContainer());
            processor.ProcessSolution(solution);
            //Fix GenerateCode -> to true for all creatd Rels.
            processor.CreatedRelationships.ForEach(rel => rel.GenerateCode = true);

            new SolutionTraverser().MarkForDeleteAllChangeTypeDelete(database);
            return errors;
        }

        private static void UpdateSolutionName(IDBDatabase database, DMSolution solution)
        {
            if(string.IsNullOrEmpty(database.DatabaseName)) return;
            if (MustBeRenamed(solution)) solution.SolutionName = GetSolutionName(database);
        }

        private static string GetSolutionName(IDBDatabase database)
        {
            return StringUtilities.PascalCaseTableName(database.DatabaseName.Trim());
        }

        private static bool MustBeRenamed(DMSolution solution)
        {
            string solutionName = solution.SolutionName;
            return (string.IsNullOrEmpty(solutionName) || solutionName == DMSolution.DEFAULT_NEW_SOLUTION_NAME); 
  
        }

        private IBusinessObject[] GetBusinessObjectArray(IBusinessObjectCollection fromBOCol)
        {
            IBusinessObject[] businessObjects = new IBusinessObject[fromBOCol.Count];
            fromBOCol.CopyTo(businessObjects, 0);
            return businessObjects;
        }
        private static void AddErrorMessages(IBusinessObject businessObject, IEnumerable<string> errorMessages, List<IBOError> errors)
        {
            errors.AddRange(errorMessages.Select(errorMessage => new BOError(errorMessage, ErrorLevel.Error) { BusinessObject = businessObject }).Cast<IBOError>());
        }

        private void ImportDBTableChanges(DMSolution dmSolution, IDBTable table)
        {
            if (!table.PendingChangeApproved) return;
            switch (table.PendingChangeType)
            {
                case ChangeType.Delete:
                    var mappedDMClass = table.GetMappedDMClass();
                    if (mappedDMClass != null) new DMClassDeleter(mappedDMClass).Delete();
                    table.MarkForDelete();
                    return;
                case ChangeType.Add:
                case ChangeType.Rename:
                case ChangeType.Update:
                    _dbToDMClassImporter.Import(dmSolution, table);
                    table.PendingChangeType = ChangeType.None;
                    break;
            }
        }

        private void ImportDBColumnChanges(IDBColumn column, List<IBOError> errors)
        {
            if (!column.PendingChangeApproved) return;
            switch (column.PendingChangeType)
            {
                case ChangeType.Delete:
                    var property = column.GetMappedDMProperty();
                    if (property != null) { new DMPropertyDeleter(property).Delete(); }
                    column.MarkForDelete();
                    break;
                case ChangeType.Add:
                case ChangeType.Rename:
                case ChangeType.Update:
                    if (column.DBTable == null) return;

                    DMClass mappedDMClass = column.DBTable.GetMappedDMClass();
                    if (mappedDMClass == null)
                    {
                        AddColumnImportErrmessage(column, errors);
                        return;
                    }
                    _dbToDMPropertyImporter.Import(mappedDMClass, column);
                    column.PendingChangeType = ChangeType.None;
                    break;
            }
        }

        private void AddColumnImportErrmessage(IDBColumn column, List<IBOError> errors)
        {
            BOError error = new BOError(string.Format(
                "Cannot add column '{0}' to class because the table '{1}' has no matching class in the domain model.",
                column.ColumnName, column.DBTable.TableName), ErrorLevel.Warning);
            error.BusinessObject = column;
            errors.Add(error);
        }

        private void ImportDBPrimaryKeyChanges(DBTable table, List<IBOError> errors)
        {
            DBPrimaryKey primaryKey = table.PrimaryKeyDB;
            if (primaryKey != null && primaryKey.PendingChangeApproved)
            {
                DMClass mappedDMClass = table.GetMappedDMClass();
                if (mappedDMClass == null)
                {
                    AddError(errors, table, primaryKey);
                    return;
                }

                switch (primaryKey.PendingChangeType)
                {
                    case ChangeType.Delete:
                        if (mappedDMClass.GetObjectID() != null) mappedDMClass.GetObjectID().MarkForDelete();                
                        mappedDMClass.ObjectIdentity = null;
                        table.PrimaryKeyDB = null;
                        primaryKey.MarkForDelete();
                        break;
                    case ChangeType.Add:
                    case ChangeType.Update:
                        IList<string> errorMessages;
                        _dbToDMObjectIdentityImporter.Import(mappedDMClass, primaryKey, out errorMessages);
                        AddErrorMessages(primaryKey, errorMessages, errors);
                        if (errorMessages.Count == 0)
                        {
                            primaryKey.PendingChangeType = ChangeType.None;
                            primaryKey.PrimaryKeyColumns.ForEach(column => column.PendingChangeType = ChangeType.None);
                        }
                        break;
                }
            }
        }

        private void AddError(List<IBOError> errors, DBTable table, DBPrimaryKey primaryKey)
        {
            BOError error = new BOError(string.Format(
                "Cannot Import PrimaryKeyChanges to class because the table '{0}' has no matching class in the domain model.",
                table.TableName), ErrorLevel.Warning);
            error.BusinessObject = primaryKey;
            errors.Add(error);
        }

        private void ImportDBKeyChanges(DBKey key, List<IBOError> errors)
        {
            if (!key.PendingChangeApproved) return;
            switch (key.PendingChangeType)
            {
                case ChangeType.Delete:
                    //TODO brett 10 Sep 2010: This gotta get more intelligent find it by matching
                    // both the props etc what if this is null
                    var uniqueConstraint = key.GetMappedDMUniqueConstraint();
                    if (uniqueConstraint != null) uniqueConstraint.MarkForDelete();
                    key.MarkForDelete();
                    break;
                case ChangeType.Add:
                case ChangeType.Update:
                    IList<string> errorMessages;
                    _dbToDMUniqueConstraintImporter.Import(key.Table.GetMappedDMClass(), key, out errorMessages);
                    AddErrorMessages(key, errorMessages, errors);
                    if (errorMessages.Count == 0)
                    {
                        key.PendingChangeType = ChangeType.None;
                        key.KeyColumns.ForEach(column => column.PendingChangeType = ChangeType.None);
                    }
                    break;
            }
        }

        private void ImportDBForeignKeyChanges(DBForeignKey key, List<IBOError> errors)
        {
            if (!key.PendingChangeApproved) return;
            switch (key.PendingChangeType)
            {
                case ChangeType.Delete:
                    if (key.GetMappedDMRelationship() != null) key.GetMappedDMRelationship().MarkForDelete();
                    key.MarkForDelete();
                    break;
                case ChangeType.Add:
                case ChangeType.Update:
                    IList<string> errorMessages;
                    _dbToDMRelationshipImporter.Import(key, out errorMessages);
                    AddErrorMessages(key, errorMessages, errors);
                    key.ForeignKeyColumns.ForEach(column => column.PendingChangeType = ChangeType.None);
                    key.PendingChangeType = ChangeType.None;
                    break;
            }
        }
    }
}